package puntaelena.facilitylocation.hillclimbing.solver.agents;

import puntaelena.graphsupport.FacilitiesGraph;
import puntaelena.facilitylocation.hillclimbing.solver.services.*;
import puntaelena.facilitylocation.hillclimbing.solver.agents.coalitionspool.CoalitionsPair;

import java.util.List;
import java.util.ArrayList;
import java.util.HashSet;

import jfuzzyprincepless.negotiation.INegotiationContext;
import jfuzzyprincepless.negotiation.DefaultNegotiationContext;

public class FcltCityNegotiateComputator {
	/**
	 * version constants:
	 */
	public static final int NOT_INIT_VERSION = -1;
	public static final int INIT_VERSION = 0;

	// cities side

	public static double computeBestWitnessProfit(
			FacilitiesGraph fgraph,
			int[] witnesses,
			boolean[] isFcltOpen,
			int city
	) {
		return computeBestWitnessProfit(
			fgraph,
			witnesses,
			isFcltOpen,
			city,
			false
		);
	}
	
	public static double computeBestAllWitnessProfit(
		FacilitiesGraph fgraph,
		int[] witnesses,
		boolean[] isFcltOpen
	) {
		return computeBestAllWitnessProfit(
			fgraph,
			witnesses,
			isFcltOpen,
			false
		);
	}
	
	public static double computeBestAllWitnessProfit(
		FacilitiesGraph fgraph,
		int[] witnesses,
		boolean[] isFcltOpen,
		boolean applyBestWitness
	) {
		int citiesCount = fgraph.getCitiesCount();
		double result = 0;
		for (int city = 0; city < citiesCount; city++) {
			result += computeBestWitnessProfit(
				fgraph,
				witnesses,
				isFcltOpen,
				city,
				applyBestWitness
			);
		}
		return result;
	}
	
	public static double computeBestWitnessProfit(
		FacilitiesGraph fgraph,
		int[] witnesses,
		boolean[] isFcltOpen,
		int city,
		boolean applyBestWitness
	) {
		int facilitiesCount = fgraph.getFacilitiesCount();
		int bestWitness = witnesses[city];
		bestWitness = isFcltOpen[bestWitness] ? bestWitness : -1;
		double bestRoadPrice = bestWitness == -1 ?
			fgraph.getInfEdgeValue() :
			fgraph.getFSEdge(city, bestWitness);

		for (int facility = 0; facility < facilitiesCount; facility++) {
			if (isFcltOpen[facility]) {
				if (bestRoadPrice > fgraph.getFSEdge(city, facility)) {
					bestWitness = facility;
					bestRoadPrice = fgraph.getFSEdge(city, facility);
				}
			}
		}
		
		if (bestWitness == -1) {
			return -fgraph.getInfEdgeValue();
		}

		double profit = fgraph.getFSEdge(city, witnesses[city]) -
			fgraph.getFSEdge(city, bestWitness);
		if (applyBestWitness) {
			witnesses[city] = bestWitness;
		}
		
		return profit;
	}

	// facilities side

	public static double computeRedistributeFacilityProfit(
		FacilitiesGraph fgraph,
		int[] witnesses,
		boolean[] isFcltOpen,
		int facility
	) {
		if (!isFcltOpen[facility]) {
			throw new IllegalArgumentException("bad state of facility");
		}

		assert _ac_allwitnessesAreOpen(witnesses, isFcltOpen);

		isFcltOpen[facility] = false;
		double result = Double.NaN;
		try {
			result = computeRedistributeClosedFacilitiesProfit(
				fgraph,
				witnesses,
				isFcltOpen
			);
		} finally {
			isFcltOpen[facility] = true;
		}

		result = Math.max(result, -fgraph.getInfEdgeValue());
		return result;
	}

	private static double computeRedistributeClosedFacilitiesProfit(
		FacilitiesGraph fgraph,
		int[] witnesses,
		boolean[] isFcltOpen
	) {
		int citiesCount = fgraph.getCitiesCount();

		double result = 0;
		for (int city = 0; city < citiesCount; city++) {
			if (!isFcltOpen[witnesses[city]]) {
				result += computeBestWitnessProfit(
					fgraph,
					witnesses,
					isFcltOpen,
					city
				);
			}
		}
		return result;
	}

	private static double computeRateSimilarity(
		double[] enemyRateA,
		double[] enemyRateB,
		FacilityCoalition coalitionA,
		FacilityCoalition coalitionB,
		IHillClimbingAgentRegistry hcagReg
	) {
		double result = 0;
		int n = enemyRateA.length;
		for (int i = 0; i < n; i++) {
			CollaboratingFacilityAgent curAg =
				hcagReg.getCollFcltAgent(i);
			if (
				coalitionA.getMembers().contains(curAg) ||
				coalitionB.getMembers().contains(curAg)
			) {
				continue;
			}
			double cur = Math.abs(enemyRateA[i] - enemyRateB[i]);
			result -= cur;
		}
		return result;
	}

	public static double computeRateSimilarity(
		FacilityCoalition coalitionA,
		FacilityCoalition coalitionB,
		IHillClimbingAgentRegistry hcagReg
	) {
		double similarityMeasure = computeRateSimilarity(
			coalitionA.getEnemyRate(),
			coalitionB.getEnemyRate(),
			coalitionA,
			coalitionB,
			hcagReg
		);
		int minSize = Math.min(
			coalitionA.getMembers().size(),
			coalitionB.getMembers().size()
		);
		int maxSize = Math.max(
			coalitionA.getMembers().size(),
			coalitionB.getMembers().size()
		);
		similarityMeasure *= minSize + Math.log(maxSize / minSize);
		return similarityMeasure;
	}

	public static double computeRateSimilarity(
		double[] enemyRateA,
		double[] enemyRateB
	) {
		return computeRateSimilarity(
			enemyRateA,
			enemyRateB,
			FacilityCoalition.EMPTY,
			FacilityCoalition.EMPTY,
			IHillClimbingAgentRegistry.EMPTY
		); 
	}
	
	public static void normRate(double[] rate, double infVal) {
		normRate(rate, infVal, 1e-5);
	}

	public static void normRate(double[] rate, double infVal, double eps) {
		replaceInf(rate, infVal, eps);
		normIntegr0(rate);
		normIntegrSq1(rate, eps);
	}

	private static void replaceInf(
		double[] rate,
		double infVal,
		double eps
	) {
		double min = infVal;
		for (double d : rate) {
			if (d > -infVal) {
				min = Math.min(min, d);
			}
		}
		
		double min1 = infVal;
		for (double d : rate) {
			if (d > min + eps) {
				min1 = Math.min(min1, d);
			}
		}
		if (min1 == infVal) {
			min1 = min;
		}
		
		double diff = min1 - min;
		int n = rate.length;
		for (int i = 0; i < n; i++) {
			if (rate[i] <= -infVal) {
				rate[i] = min - diff;
			}
		}
	}

	private static void normIntegrSq1(double[] rate, double eps) {
		int n = rate.length;
		double sum = 0;
		for (int i = 0; i < n; i++) {
			sum += rate[i] * rate[i];
		}
		double mul = Math.abs(sum) < eps ? 1 : 1 / Math.sqrt(sum);
		for (int i = 0; i < n; i++) {
			rate[i] *= mul;
		}
	}

	private static void normIntegr0(double[] rate) {
		int n = rate.length;
		double sum = 0;
		for (int i = 0; i < n; i++) {
			sum += rate[i];
		}
		double aver = sum / n;
		for (int i = 0; i < n; i++) {
			rate[i] -= aver;
		}
	}

	public static void markTemporaryOpen(
		FacilityCoalition coalition,
		IFcltLocDataService flServ
	) {
		mark(coalition, flServ, true);
	}

	public static void unmarkTemporaryOpen(
		FacilityCoalition coalition,
		IFcltLocDataService flServ
	) {
		mark(coalition, flServ, false);
	}

	private static void mark(
		FacilityCoalition coalition,
		IFcltLocDataService flServ,
		boolean theMark
	) {
		boolean[] isFcltOpen = flServ.getFcltOpen();
		for (CollaboratingFacilityAgent fagent : coalition.getMembers()) {
			if (isFcltOpen[fagent.getFacility()] == theMark) {
				throw new IllegalArgumentException("Invalid member");
			}
			isFcltOpen[fagent.getFacility()] = theMark;
		}
	}

	public static double computeCoalitionCost(
		FacilitiesGraph fgraph,
		FacilityCoalition coalition
	) {
		double result = 0;
		double[] facilitiesPrices = fgraph.getFacilitiesPrices();
		for (CollaboratingFacilityAgent agent : coalition.getMembers()) {
			result += facilitiesPrices[agent.getFacility()];
		}
		return result;
	}

	public static List<FacilityCoalition> getAllCoalitions(
		IHillClimbingAgentRegistry hcagReg,
		IFcltLocDataService flServ
	) {
		List<FacilityCoalition> coalitions =
			new ArrayList<FacilityCoalition>();
		boolean[] isFcltOpen = flServ.getFcltOpen();
		for (
			CollaboratingFacilityAgent agent :
				hcagReg.getFacilityAgents()
		) {
			if (!isFcltOpen[agent.getFacility()]) {
				CollaboratingFacilityAgent cfag = agent;
				FacilityCoalition coalition = cfag.getCoalition(flServ);
				if (coalition.isOwner(cfag)) {
					coalitions.add(coalition);
				}
			}
		}
		assert coalitions.size() == new HashSet<FacilityCoalition>(coalitions).size();
		return coalitions;
	}
	
	// services + context

	public static void initHillClimbingServices(
		INegotiationContext context,
		FacilitiesGraph fgraph,
		boolean[] isFcltOpen,
		int[] witnesses
	) {
		IFcltLocDataService flServ =
			context.getService(IFcltLocDataService.class);
		flServ.setFacilityLocationData(fgraph, isFcltOpen, witnesses);
		IHillClimbingAgentRegistry hcagReg =
			context.getService(IHillClimbingAgentRegistry.class);
		hcagReg.init(fgraph);
	}

	public static INegotiationContext createHillClimbingNegotiationContext() {
		DefaultNegotiationContext context =
			new DefaultNegotiationContext();
		context.registerService(
			new DefaultFcltLocDataService()
		);
		context.registerService(
			new DefaultHillClimbingAgentRegistry()
		);
		context.registerService(
			new NOPFcltCoalitionEventsListener()
		);
		context.registerService(
			ICoalitionsListener.NOP_LISTENER
		);
		return context;
	}

	// assertion checks

	private static boolean _ac_allwitnessesAreOpen(
		int[] witnesses,
		boolean[] isFcltOpen
	) {
		for (int witness : witnesses) {
			assert isFcltOpen[witness];
			if (!isFcltOpen[witness]) {
				return false;
			}
		}
		return true;
	}

	public static boolean pairsEqual(
		CoalitionsPair pair0,
		CoalitionsPair pair1,
		double eps
	) {
		return pairsEqual(pair0, pair1, true, eps);
	}

	public static boolean pairsEqual(
		CoalitionsPair pair0,
		CoalitionsPair pair1,
		boolean compareSimilarity,
		double eps
	) {
		if (pair0 == null || pair1 == null) {
			return pair0 == null && pair1 == null;
		}
		return	(pair0.coalA == pair1.coalA && pair0.coalB == pair1.coalB ||
				 pair0.coalA == pair1.coalB && pair0.coalB == pair1.coalA) &&
				(!compareSimilarity || Math.abs(pair0.getSimMsr() - pair1.getSimMsr()) < eps);
	}
}
